package com.bruce.hmily_provider_user.service.impl;

import com.bruce.hmily_entity.*;
import com.bruce.hmily_provider_user.feign.FeignUserCoupon;
import com.bruce.hmily_provider_user.service.UserService;
import com.bruce.hmily_repository.LocalCancelRepository;
import com.bruce.hmily_repository.LocalConfirmRepository;
import com.bruce.hmily_repository.LocalTryRepository;
import com.bruce.hmily_repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hmily.annotation.Hmily;
import org.dromara.hmily.core.concurrent.threadlocal.HmilyTransactionContextLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.HashMap;

/**
 * 类注释
 *
 * @author Bruce
 * @date 2021/3/22 3:55 PM
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private FeignUserCoupon userCoupon;

    @Autowired
    private LocalTryRepository tryRepository;
    @Autowired
    private LocalConfirmRepository confirmRepository;
    @Autowired
    private LocalCancelRepository cancelRepository;

    /**
     * 更新用户信息
     * @param userInfo
     * @return
     */
    @Override
    @Transactional
    public User save(User userInfo) {
        User user = userRepository.save(userInfo);
        return user;
    }

    /**
     * 获取用户根据用户ID
     *
     * @param id
     * @return
     */
    @Override
    public User findByUserId(long id) {
        return userRepository.findByUserId(id);
    }

    /**
     * 主事务操作写在try方法中，子事务操作写在confirm方法中
     * @return
     */
    @Hmily(confirmMethod = "saveConfirm", cancelMethod = "saveCancel")
    @Transactional
    @Override
    public boolean testDTrans() {
        log.info("------->User testDTrans begin");

        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("------->User testDTrans transId:" + transId);
        //1.幂等校验
        if (tryRepository.existsById(transId)) {
            log.info(String.format("------->User testDTrans 幂等校验，此transId:%s已经执行，无须重复执行", transId));
            return false;
        }
        //2.悬挂处理
        if (cancelRepository.existsById(transId) || confirmRepository.existsById(transId)) {
            log.info(String.format("------->User testDTrans 悬挂处理，此transId:%s已经执行confirm或cancel，不可继续执行", transId));
            return false;
        }

        User user = findByUserId(1);
        log.info(String.format("------->User testDTrans A-userId:%d, userMondy:%f", user.getUserId(), user.getUserMoney()));
        //3.执行操作 减去消费金额
        double consumptionMoney = 100;
        user.setUserMoney(user.getUserMoney()-consumptionMoney);
        save(user);

        //4.用户帐户余额不足异常，应提前判断，写在此为了测试回滚，此处用到了本地事务回滚，此处回滚尚未调取feign业务，本地事务回滚即可
        log.info(String.format("------->User testDTrans B-userId:%d, userMondy:%f", user.getUserId(), user.getUserMoney()));
        if(user.getUserMoney()<0){
            throw new RuntimeException("用户帐户余额不足异常，应提前判断，写在此为了测试回滚");
        }

        //5.幂等插入，如果执行到此位置发生异常，则执行本地事务回滚即可
        LocalTryLog tryLog = new LocalTryLog();
        tryLog.setTx_no(transId);
        tryLog.setCreate_time(Calendar.getInstance().getTime());
        tryRepository.save(tryLog);

        //6.发一张消费金额1/10的优惠券
        Object updateVal = userCoupon.update(user.getUserId(), 0, consumptionMoney*0.1);
        //7.如果调取失败，则进行本地事务回滚，此处虽然已进行LocalTryLog日志插入，但在cancel方法中无须处理，因为本地事务已经回滚
        if(updateVal == null || updateVal.toString().contains("error")){
            log.error("------->User testDTrans 发放优惠券异常");
            throw new RuntimeException("发放优惠券异常");
        }

        //模拟异常
        long tmpVal = System.currentTimeMillis();
        if (tmpVal % 2 == 0) {
            log.info("------->User testDTrans 模拟异常");
            throw new RuntimeException("模拟异常");
        }

        log.info("------->User testDTrans End");
        return true;
    }

    /**
     * 主事务confirm方法执行完成后，子事务的confirm方法才开始执行
     * @return
     */
    public boolean saveConfirm() {
        log.info("------->User saveConfirm begin");

        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("------->User saveConfirm transId:" + transId);

        //1.幂等校验
        if (confirmRepository.existsById(transId)) {
            log.info(String.format("------->User saveConfirm 幂等校验，此transId:%s已经执行，无须重复执行", transId));
            return false;
        }

        //2.执行操作
        try {
            //TODO 需要执行的操作，此处的工作放在try中执行
        }
        catch (Exception ex){
            //如果操作失败，则需要通知系统，此数据必须成功
            //TODO 将需要操作的数据插入消息队列，由另外服务时时处理此类型的数据
            log.error("------->User saveConfirm error，此UserCouponId数据必须成功");
        }

        //3.幂等插入
        LocalConfirmLog confirmLog = new LocalConfirmLog();
        confirmLog.setTx_no(transId);
        confirmLog.setCreate_time(Calendar.getInstance().getTime());
        confirmRepository.save(confirmLog);

        log.info("------->User saveConfirm end");
        return true;
    }

    /**
     * 如果有异常发生，则判断是否已经插入数据库，如果已经插入数据库，则需要删除已插入的数据
     * @return
     */
    public boolean saveCancel() {
        log.info("------->User saveCancel begin");

        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("------->User saveCancel transId:" + transId);

        //1.幂等校验
        if (cancelRepository.existsById(transId)) {
            log.info(String.format("------->User saveCancel 幂等校验，此transId:%s已经执行，无须重复执行", transId));
            return false;
        }
        //2.空回滚处理
        if (!tryRepository.existsById(transId)) {
            log.info(String.format("------->User saveCancel 空回滚，此transId:%s对应的try尚未执行完成，不可继续执行", transId));
            return false;

        }

        //3.回滚处理，因try用到了本地事务，所以发生异常时，已在try中进行本地事务回滚，此处无须回滚

        //4.幂等插入
        LocalCancelLog cancelLog = new LocalCancelLog();
        cancelLog.setTx_no(transId);
        cancelLog.setCreate_time(Calendar.getInstance().getTime());
        cancelRepository.save(cancelLog);

        log.info("------->User saveCancel end");
        return false;
    }
}
